package dbx

import (
	"context"
	"errors"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"go.uber.org/zap"
	"gorm.io/gorm"
	gl "gorm.io/gorm/logger"
	"gorm.io/gorm/utils"
)

// 设置zap日志
func setZapLogger(z *zap.Logger) zLogger {
	return zLogger{
		ZapLogger:     z,
		LogLevel:      gl.Info,
		SlowThreshold: 500 * time.Millisecond,
	}
}

// 以下都是实现gorm日志interface
type zLogger struct {
	ZapLogger     *zap.Logger
	LogLevel      gl.LogLevel
	SlowThreshold time.Duration
}

func (l zLogger) LogMode(level gl.LogLevel) gl.Interface {
	return zLogger{
		ZapLogger:     l.ZapLogger,
		SlowThreshold: l.SlowThreshold,
		LogLevel:      level,
	}
}

func (l zLogger) Info(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel < gl.Info {
		return
	}
	l.logger().Sugar().Infof(msg, data)
}

func (l zLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel < gl.Warn {
		return
	}
	l.logger().Sugar().Warnf(msg, data)
}

func (l zLogger) Error(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel < gl.Error {
		return
	}
	l.logger().Sugar().Errorf(msg, data)
}

func (l zLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	if l.LogLevel <= 0 {
		return
	}

	// 解析出context里的request-id
	var requestId string
	if f, ok := ctx.Value("RequestId").(string); ok {
		requestId = f
	}

	// 处理sql日志
	elapsed := time.Since(begin)
	switch {
	case err != nil && l.LogLevel >= gl.Error:
		sql, rows := fc()
		if errors.Is(err, gorm.ErrRecordNotFound) {
			l.logger().Info("sql_trace",
				zap.String("sql_trace_level", "info"),
				zap.Float64("elapsed_ms", float64(elapsed.Nanoseconds())/1e6),
				zap.Int64("rows", rows),
				zap.String("sql", sql),
				zap.String("request_id", requestId),
				zap.String("file", utils.FileWithLineNum()),
			)
		} else {
			l.logger().Error("sql_trace",
				zap.String("sql_trace_level", "error"),
				zap.Error(err),
				zap.Float64("elapsed_ms", float64(elapsed.Nanoseconds())/1e6),
				zap.Int64("rows", rows),
				zap.String("sql", sql),
				zap.String("request_id", requestId),
				zap.String("file", utils.FileWithLineNum()),
			)
		}
	case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= gl.Warn:
		sql, rows := fc()
		l.logger().Warn("sql_trace",
			zap.String("sql_trace_level", "warn"),
			zap.Float64("elapsed_ms", float64(elapsed.Nanoseconds())/1e6),
			zap.Int64("rows", rows),
			zap.String("sql", sql),
			zap.String("request_id", requestId),
			zap.String("file", utils.FileWithLineNum()),
		)
	case l.LogLevel >= gl.Info:
		sql, rows := fc()
		l.logger().Info("sql_trace",
			zap.String("sql_trace_level", "info"),
			zap.Float64("elapsed_ms", float64(elapsed.Nanoseconds())/1e6),
			zap.Int64("rows", rows),
			zap.String("sql", sql),
			zap.String("request_id", requestId),
			zap.String("file", utils.FileWithLineNum()),
		)
	}
}

var (
	gormPackage    = filepath.Join("gorm.io", "gorm")
	zapGormPackage = filepath.Join("moul.io", "zapgorm2")
)

func (l zLogger) logger() *zap.Logger {
	for i := 2; i < 15; i++ {
		_, file, _, ok := runtime.Caller(i)
		switch {
		case !ok:
		case strings.HasSuffix(file, "_test.go"):
		case strings.Contains(file, gormPackage):
		case strings.Contains(file, zapGormPackage):
		default:
			return l.ZapLogger.WithOptions(zap.AddCallerSkip(i))
		}
	}
	return l.ZapLogger
}
